package net.awpspace.walkman.view;

import java.util.Vector;

import javax.microedition.media.MediaException;

import net.awpspace.walkman.piano.InstrumentEvent;
import net.awpspace.walkman.piano.InstrumentModelListener;
import net.awpspace.walkman.piano.MIDIPlayer;
import net.awpspace.walkman.piano.NoteEvent;
import net.awpspace.walkman.piano.NotePlayer;
import net.awpspace.walkman.piano.PianoModel;
import net.awpspace.walkman.piano.PianoNotes;
import net.awpspace.walkman.piano.TonePlayer;

/**
 * Container for Piano Canvas
 * 
 * @author Diep AwpSpace
 * 
 */
public class PianoHolder implements PianoModel, PianoNotes {
	
	private NotePlayer player;
	private Thread playerThread;

	private int octave;
	private boolean[] keyPressed;

	private Vector noteEvents;
	private Vector listeners;

	/**
	 * Constructor of the MIDlet.
	 * 
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	public PianoHolder() {
		keyPressed = new boolean[MIDI_KEYS];
		noteEvents = new Vector();
		listeners = new Vector(1);
	}

	/**
	 * Handler of the starting event.
	 */
	public void startApp() {
		// Initialize piano model
		octave = 2;
		for (int i = 0; i < MIDI_KEYS; i++)
			keyPressed[i] = false;

		try {
			player = new MIDIPlayer(this);
		} catch (Exception em) {
			try {
				player = new TonePlayer(this);
			} catch (Exception et) {
			}
		}

		playerThread = new Thread(player);
		playerThread.start();
	}

	/**
	 * Handler of the destroying event.
	 * 
	 * @param unconditional
	 *            Flag to indicate whether destroying is unconditional
	 */
	public void destroyApp(boolean unconditional) {
		try {
			player.stop();
		} catch (MediaException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Implementation of PianoModel.
	 */
	public int getOctave() {
		return octave;
	}

	/**
	 * Implementation of PianoModel.
	 * 
	 * @throws ArrayIndexOutOfBoundsException
	 */
	public void setOctave(int octave) {
		// Calculate index of the lowest key (C) in the new octave
		int keyIndex = MIDI_MIDDLE_C + octave * OCTAVE_NOTES;

		if (keyIndex > 0 && keyIndex <= MIDI_KEYS - OCTAVE_NOTES) {
			this.octave = octave;
		} else
			throw new ArrayIndexOutOfBoundsException();
	}

	/**
	 * Implementation of PianoModel.
	 */
	public boolean[] getCurrentOctaveKeys() {
		boolean[] keys = new boolean[OCTAVE_NOTES];

		System.arraycopy(keyPressed, MIDI_MIDDLE_C + octave * OCTAVE_NOTES,
				keys, NOTE_C, OCTAVE_NOTES);

		return keys;
	}

	/**
	 * Implementation of PianoModel.
	 */
	public boolean hasMoreNoteEvents() {
		return !noteEvents.isEmpty();
	}

	/**
	 * Implementation of PianoModel.
	 */
	public NoteEvent nextNoteEvent() {
		// Get first element and remove it from the queue
		NoteEvent ev = (NoteEvent) noteEvents.firstElement();
		noteEvents.removeElementAt(0);

		return ev;
	}

	/**
	 * Implementation of PianoModel.
	 */
	public void addInstrumentModelListener(InstrumentModelListener listener) {
		listeners.addElement(listener);
	}

	/**
	 * Implementation of PianoModel.
	 */
	public void removeInstrumentModelListener(InstrumentModelListener listener) {
		listeners.removeElement(listener);
	}

	/**
	 * Implementation of PianoModel.
	 */
	public void processEvent(InstrumentEvent e) {
		boolean notify = true;
		int key;
		NoteEvent ev;

		switch (e.getCode()) {
		case InstrumentEvent.KEY_PRESSED:
			key = MIDI_MIDDLE_C + octave * OCTAVE_NOTES + e.getControl();
			keyPressed[key] = true;
			ev = new NoteEvent(NoteEvent.NOTE_ON, key, 100);
			noteEvents.addElement(ev);
			break;

		case InstrumentEvent.KEY_RELEASED:
			key = MIDI_MIDDLE_C + octave * OCTAVE_NOTES + e.getControl();
			keyPressed[key] = false;
			ev = new NoteEvent(NoteEvent.NOTE_OFF, key, 0);
			noteEvents.addElement(ev);
			break;

		case InstrumentEvent.OCTAVE_UP:
			setOctave(getOctave() + 1);
			break;

		case InstrumentEvent.OCTAVE_DOWN:
			setOctave(getOctave() - 1);
			break;

		default:
			notify = false;
			break;
		}

		// Notify listeners if needed
		if (notify)
			notifyListeners();
	}

	/**
	 * Implementation of PianoModel.
	 */
	public void notifyListeners() {
		for (int i = 0; i < listeners.size(); i++) {
			((InstrumentModelListener) listeners.elementAt(i)).update();
		}
	}
}
